//
//  SDJSONModel.m
//  SKinDiary
//
//  Created by pcyang on 16/7/7.
//  Copyright © 2016年 tencent. All rights reserved.
//

#import "SDJSONModel.h"
#import <objc/runtime.h>

@interface NSString (SDNumber)
- (char)charValue;
- (unsigned char)unsignedCharValue;
- (short)shortValue;
- (unsigned short)unsignedShortValue;
- (unsigned int)unsignedIntValue;
- (long)longValue;
- (unsigned long)unsignedLongValue;
- (unsigned long long)unsignedLongLongValue;
- (NSUInteger)unsignedIntegerValue;
@end

@implementation NSString (SDNumber)
- (char)charValue
{
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    NSNumber* num = [numberFormatter numberFromString:self];
    return [num charValue];
}

- (unsigned char)unsignedCharValue
{
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    NSNumber* num = [numberFormatter numberFromString:self];
    return [num unsignedCharValue];
}

- (short)shortValue
{
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    NSNumber* num = [numberFormatter numberFromString:self];
    return [num shortValue];
}

- (unsigned short)unsignedShortValue
{
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    NSNumber* num = [numberFormatter numberFromString:self];
    return [num unsignedShortValue];
}

- (unsigned int)unsignedIntValue
{
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    NSNumber* num = [numberFormatter numberFromString:self];
    return [num unsignedIntValue];
}

- (long)longValue
{
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    NSNumber* num = [numberFormatter numberFromString:self];
    return [num longValue];
}

- (unsigned long)unsignedLongValue
{
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    NSNumber* num = [numberFormatter numberFromString:self];
    return [num unsignedLongValue];
}

- (unsigned long long)unsignedLongLongValue
{
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    NSNumber* num = [numberFormatter numberFromString:self];
    return [num unsignedLongLongValue];
}

- (NSUInteger)unsignedIntegerValue
{
    NSNumberFormatter *numberFormatter = [[NSNumberFormatter alloc] init];
    NSNumber* num = [numberFormatter numberFromString:self];
    return [num unsignedIntegerValue];
}
@end

typedef NS_ENUM(NSUInteger, SDPropertyType){
    SDPropertyType_StringNumber,    //对应NSString可以转变的基础数据类型
    SDPropertyType_NormalNumber,    //对应其他NSNumber可以转变的基础数据类型
    SDPropertyType_String,          //对应NSString类型
    SDPropertyType_Dictionary,      //对应NSDictionary类型
    SDPropertyType_Array,           //对应NSArray类型
    SDPropertyType_Object,
    SDPropertyType_UnSupported,
};

@interface SDPropertyInfo : NSObject
@property (nonatomic,strong) NSString       *name;
@property (nonatomic,strong) Class          class;
@property (nonatomic,assign) SDPropertyType type;
@end

@implementation SDPropertyInfo
@end

static void *SDJSONModelCachedPropertyiesInfoKey = &SDJSONModelCachedPropertyiesInfoKey;
//static void *SDJSONModelCachedIgnoreCodingPropertyiesInfoKey = &SDJSONModelCachedIgnoreCodingPropertyiesInfoKey;

@implementation SDJSONModel (Object_Parser)
+ (NSDictionary*)allPropertiesInfo
{
    return objc_getAssociatedObject(self, SDJSONModelCachedPropertyiesInfoKey);
}

//+ (NSSet*)allIgnoreCodingPropertyNames
//{
//    return objc_getAssociatedObject(self, SDJSONModelCachedIgnoreCodingPropertyiesInfoKey);
//}

+ (SDPropertyInfo*)propertyInfoWithName:(NSString*)name
{
    Class class = self;
    do {
        NSDictionary* allInfo = [class allPropertiesInfo];
        SDPropertyInfo* info = [allInfo objectForKey:name];
        if( info )return info;
        
        class = [class superclass];
        if( class == [SDJSONModel class] )break;
    } while (TRUE);
    return nil;
}

+ (void)initialize
{
    [super initialize];
    
    if( self == [SDJSONModel class] )return;
    
    unsigned int outCount, i;
    objc_property_t *properties = class_copyPropertyList(self, &outCount);
    
    NSMutableDictionary* info = [NSMutableDictionary dictionaryWithCapacity:outCount];
    for (i = 0; i < outCount; i++) {
        objc_property_t property = properties[i];
        
        SDPropertyInfo* propertyInfo = [[SDPropertyInfo alloc]init];
        propertyInfo.name = [NSString stringWithUTF8String:property_getName(property)];
        
        
        const char *propertyAttributes = property_getAttributes(property);
        switch (*(propertyAttributes+1)) {
            case 'd'://double
            case 'f'://float
            case 'i'://int//enum
            case 'q'://long long
            case 'B'://bool
            case 'I'://unsigned int  [@"123a" respondsToSelector:@selector(unsignedIntValue)]
                propertyInfo.type = SDPropertyType_StringNumber;
                break;
            case 'c'://char//BOOL
            case 'l'://long
            case 's'://short
            case 'C'://unsigned char
            case 'L'://unsigned long
            case 'Q'://unsigned long long
            case 'S'://unsigned short
                propertyInfo.type = SDPropertyType_NormalNumber;
                break;
            case '@'://OBC Object
            {
                size_t commaPos = strcspn(propertyAttributes+2,",");
                NSString* newString = [[NSString alloc]initWithBytes:propertyAttributes+2 length:commaPos encoding:NSASCIIStringEncoding];
                newString = [newString stringByReplacingOccurrencesOfString:@"\"" withString:@""];
                
                if( [newString isEqualToString:@"NSArray"] || [newString isEqualToString:@"NSMutableArray"] )
                {
                    propertyInfo.type = SDPropertyType_Array;
                    propertyInfo.class = NSClassFromString(@"NSArray");
                }
                else if( [newString isEqualToString:@"NSDictionary"] || [newString isEqualToString:@"NSMutableDictionary"] )
                {
                    propertyInfo.type = SDPropertyType_Dictionary;
                    propertyInfo.class = NSClassFromString(@"NSDictionary");
                }
                else if( [newString isEqualToString:@"NSString"] )
                {
                    propertyInfo.type = SDPropertyType_String;
                    propertyInfo.class = NSClassFromString(@"NSString");
                }
                else
                {
                    propertyInfo.type = SDPropertyType_Object;
                    propertyInfo.class = NSClassFromString(newString);
                }
            }
                break;
            case '{'://struct//union
            case '^'://point
            default:
                propertyInfo.type = SDPropertyType_UnSupported;
                break;
        }
        
        [info setObject:propertyInfo forKey:propertyInfo.name];
    }
    
    free(properties);
    
    //    Class superClass = [self superclass];
    //    if( [superClass isSubclassOfClass:[SDJSONModel class]] )
    //        [info addEntriesFromDictionary:[superClass allPropertiesInfo]];
    
    objc_setAssociatedObject(self, SDJSONModelCachedPropertyiesInfoKey, [NSDictionary dictionaryWithDictionary:info], OBJC_ASSOCIATION_RETAIN);
    
    
    //    NSMutableSet* allIgnoredProperties = [NSMutableSet setWithCapacity:100];
    //    if( [self.class respondsToSelector:@selector(ignoredPropertiesWhenEncoding)] )
    //    {
    //        NSSet* curIgnoredProperties = [self.class ignoredPropertiesWhenEncoding];
    //        if( [curIgnoredProperties isKindOfClass:[NSSet class]] )
    //            [allIgnoredProperties intersectSet:curIgnoredProperties];
    //    }
    //    if( [superClass isSubclassOfClass:[SDJSONModel class]] )
    //    {
    //        NSSet* curIgnoredProperties = [superClass allIgnoreCodingPropertyNames];
    //        if( [curIgnoredProperties isKindOfClass:[NSSet class]] )
    //            [allIgnoredProperties intersectSet:curIgnoredProperties];
    //    }
    //
    //    objc_setAssociatedObject(self, SDJSONModelCachedIgnoreCodingPropertyiesInfoKey, [NSSet setWithSet:allIgnoredProperties], OBJC_ASSOCIATION_RETAIN);
}

+ (BOOL)isValidValue:(NSObject*)value settingToProperty:(NSString*)propertyName
{
    SDPropertyInfo* propertyInfo = [self.class propertyInfoWithName:propertyName];
    
    if( propertyInfo == nil )return NO;
    
    switch (propertyInfo.type) {
        case    SDPropertyType_StringNumber:    //对应NSString可以转变的基础数据类型
        case    SDPropertyType_NormalNumber:    //对应其他NSNumber可以转变的基础数据类型
            if( [value isKindOfClass:[NSString class]] )return TRUE;
            if( [value isKindOfClass:[NSNumber class]] )return TRUE;
            break;
        case    SDPropertyType_String:          //对应NSString类型
        case    SDPropertyType_Dictionary:      //对应NSDictionary类型
        case    SDPropertyType_Array:           //对应NSArray类型
        case    SDPropertyType_Object:
            return [value isKindOfClass:propertyInfo.class];
            break;
        case    SDPropertyType_UnSupported:
        default:
            return NO;
            break;
    }
    
    return NO;
}

- (void)securedSetValue:(id)value forKey:(NSString *)key
{
    @try {
        if( [self.class isValidValue:value settingToProperty:key] )
            [self setValue:value forKey:key];
    }
    @catch (NSException *exception) {
    }
}


- (void)parseFromInfo:(NSDictionary*)info
{
    if( [info isKindOfClass:[NSDictionary class]] == FALSE )return;
    
    if( [self respondsToSelector:@selector(manualResetWithInfo:)] )
        [self manualResetWithInfo:info];
    else
        [self automaticParseFromInfo:info];
}

- (void)automaticParseFromInfo:(NSDictionary*)info
{
    if( [info isKindOfClass:[NSDictionary class]] == FALSE )return;
    
    [info enumerateKeysAndObjectsUsingBlock:^(NSString* key1, id obj, BOOL *stop) {
        if( [key1 isKindOfClass:[NSString class]] == FALSE )return;
        
        NSString* key = nil;
        if( [self.class respondsToSelector:@selector(propertyNameFromParsedKey:)] )
            key = [self.class propertyNameFromParsedKey:key1];
        if( key == nil )key = key1;
        
        if( [obj isKindOfClass:[NSNull class]] || [obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]] )
        {
            [self securedSetValue:obj forKey:key];
        }
        else if( [obj isKindOfClass:[NSDictionary class]] )
        {
            SDPropertyInfo* propertyInfo = [self.class propertyInfoWithName:key];
            
            if( propertyInfo && propertyInfo.type == SDPropertyType_Dictionary )
            {
                [self securedSetValue:obj forKey:key];
            }
            else if (propertyInfo && propertyInfo.type == SDPropertyType_Object && [propertyInfo.class isSubclassOfClass:[SDJSONModel class]] )
            {
                SDJSONModel* subObject = [[propertyInfo.class alloc]initWithInfo:obj];
                if(subObject)[self securedSetValue:subObject forKey:key];
            }
        }
        else if( [obj isKindOfClass:[NSArray class]] )
        {
            SDPropertyInfo* propertyInfo = [self.class propertyInfoWithName:key];
            
            if( propertyInfo && propertyInfo.type == SDPropertyType_Array )
            {
                NSString* targetClassName = nil;
                if([self.class respondsToSelector:@selector(classNameForObjectInArrayProperty:)])
                    targetClassName = [self.class classNameForObjectInArrayProperty:key];
                Class targetClass = nil;
                if( [targetClassName isKindOfClass:[NSString class]] && [targetClassName length]>0 )
                    targetClass = NSClassFromString(targetClassName);
                
                if( targetClassName == nil || [targetClassName isKindOfClass:[SDJSONModel class]] == FALSE )
                    [self securedSetValue:obj forKey:key];
                else
                {
                    NSArray* subObject = [targetClass parsedArrayFromInfos:obj];
                    if(subObject)[self securedSetValue:subObject forKey:key];
                }
            }
        }
        
    }];
}

+ (NSArray*)parsedArrayFromInfos:(NSArray*)infos
{
    if( [infos isKindOfClass:[NSArray class]] == FALSE )return nil;
    
    NSMutableArray* returnedValue = [NSMutableArray arrayWithCapacity:[infos count]];
    [infos enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        if( [obj isKindOfClass:[NSDictionary class]] )
        {
            [returnedValue addObject:[[self alloc]initWithInfo:obj]];
        }
        else
        {
            [returnedValue addObject:obj];
        }
    }];
    return [NSArray arrayWithArray:returnedValue];
}

+ (NSArray*)modelsWithInfos:(NSArray*)infos
{
    return [self parsedArrayFromInfos:infos];
}

- (void)resetWithInfo:(NSDictionary*)info
{
    [self automaticParseFromInfo:info];
}
@end

@interface NSObject (SDJSONModel) <NSCoding>

@end

@implementation SDJSONModel (Object_Coding)
- (void)encodeWithCoder:(NSCoder *)aCoder
{
    Class class = self.class;
    
    do {
        NSDictionary* proerties = [class allPropertiesInfo];
        NSSet* ignoredProperties = nil;
        if( [class respondsToSelector:@selector(ignoredPropertiesWhenEncoding)] )
            ignoredProperties = [class ignoredPropertiesWhenEncoding];
        
        for (NSString* propertyName in proerties) {
            if( [ignoredProperties containsObject:propertyName] )continue;
            id value = [self valueForKey:propertyName];
            if( [value isKindOfClass:[NSNull class]] )continue;
            [aCoder encodeObject:value forKey:propertyName];
        }
        
        class = class.superclass;
        if( class == [SDJSONModel class] )break;
    } while (YES);
}

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super init];
    if( self )
    {
        Class class = self.class;
        do {
            NSDictionary* proerties = [class allPropertiesInfo];
            NSSet* ignoredProperties = nil;
            if( [class respondsToSelector:@selector(ignoredPropertiesWhenEncoding)] )
                ignoredProperties = [class ignoredPropertiesWhenEncoding];
            for (NSString* propertyName in proerties) {
                if( [ignoredProperties containsObject:propertyName] )continue;
                [self securedSetValue:[aDecoder decodeObjectForKey:propertyName] forKey:propertyName];
            }
            class = class.superclass;
            if( class == [SDJSONModel class] )break;
        } while (YES);
        
    }
    return self;
}
@end

@implementation SDJSONModel
- (id)initWithInfo:(NSDictionary *)info
{
    self = [super init];
    if( self )
    {
        [self parseFromInfo:info];
    }
    return self;
}



@end
